home *** CD-ROM | disk | FTP | other *** search
- /*
- * changetac
- *
- * This program maintains the /etc/tacpasswd file. It allows users to
- * change their own TACACS entries.
- *
- * The password generator routines are from Brandon Allbery, as
- * distribured in comp.sources.misc, volume 5, pwgen.
- *
- * Written by: David Hampton, 20 February 1990.
- * From the tacpaswd program written by: David Hampton, 30 June 89.
- */
-
- #ifdef SYSV
- #define random rand
- #define srandom srand
- #endif
-
- /*
- Path: xanth!mcnc!gatech!cwjcc!hal!ncoast!allbery
- From: allbery@ncoast.UUCP (Brandon S. Allbery)
- Newsgroups: comp.sources.misc
- Subject: v05i059: pwgen -- random-but-pronounceable password generator
- Message-ID: <13184@ncoast.UUCP>
- Date: 26 Nov 88 04:44:18 GMT
- Sender: allbery@ncoast.UUCP
- Reply-To: allbery@ncoast.UUCP (Brandon S. Allbery)
- Organization: Cleveland Public Access UN*X, Cleveland, Oh
- Lines: 328
- Approved: allbery@ncoast.UUCP
-
- Posting-number: Volume 5, Issue 59
- Submitted-by: "Brandon S. Allbery" <allbery@ncoast.UUCP>
- Archive-name: pwgen
-
- [Dr. Jekyll and Mr. Hyde, anyone? ;-) ++bsa]
-
- I've described this program, in its OSI Superboard-II incarnation, in
- comp.unix.wizards and news.sysadmin. Now, here's the code for a UN*X
- version. It seems to work OK on ncoast, except for some oddness in the
- "random" selections -- which are probably the result of our not having a
- good RNG. (Could someone mail me one or more of the alternative
- generators? Or maybe even the source to BSD random(), assuming that it
- falls outside the subset of BSD code that can be traced to AT&T? To put it
- mildly, our random number generator isn't.)
-
- This is not as complete as the original program, which actually had a list
- of characters (the current one uses phonemes) which were most likely to
- follow other phonemes. On the other hand, this version does use phonemes
- rather than characters, so its creations are more pronounceable as a rule
- than those of the original. The resulting passwords aren't quite as
- "natural" as the original, however. (That may be for the best; the original
- was intended as a simple experiment to see if a rule for "proper English
- words" could be defined in terms of rules which related letters. This was
- before I got to college and learned that such things were rather unlikely,
- but the original program still did a pretty good job of spewing out some
- common English words. That can't be said for *this* program.)
-
- To compile: cc -O -o pwgen pwgen.c -lm
- (The sqrt() function is used in an attempt to produce a random number which
- is "weighted" toward one end of the range, so as to prefer one end of a list
- of "spellings" for a phoneme over the other end. It seems to work, but with
- this rotted RNG, I can't be absolutely certain. A trial distribution seems
- to be correct, however.)
-
- What's the intent? I find that I can remember a "word" better if I can
- pronounce it. This may or may not be true for other people, but *anything*
- that encourages the use of random passwords is an improvement on what I
- typically see at client sites every day. So this program spits out
- passwords which are virtually guaranteed not to be found in the dictionary,
- but are (usually) pronounceable to speakers of English. (The tables can be
- modified for other languages, but they're rather hairy. Perhaps I'll write
- a version which loads "compiled" language descriptions, and let the compiler
- deal with the hairy stuff. Hey, they were even hairier in the BASIC
- version!)
-
- Oh, well, shar and enjoy.
-
- ++Brandon, sitting on the other side of the fence for the moment
- */
-
- /*
- * Generate (hopefully) pronounceable random passwords. These can often be
- * remembered more easily than completely random passwords, and are immune to
- * dictionary searches, etc.
- *
- * The original version of this program was written in BASIC on an OSI
- * Superboard II SBC. That version is long gone (the SB2's cassette drive
- * was never trustworthy, it eventually scrambled the tape), but the basic
- * (pardon the pun) idea lives on here, with a few modification like basing
- * the selection on "graphs" (actually, they are a restricted set of phonemes)
- * and having randomly-selected spellings for those graphs.
- */
-
- #include <stdio.h>
- #include <math.h>
-
- #define RANDOM(c) ((int) (((random(c) & 0x7fff) / 32767.0) * (c)))
-
- char *spelling[] = {
- /*a*/ "a", (char *) 0, /* 2*/
- /*A*/ "a", "ae", "ai", (char *) 0, /* 6*/
- /*b*/ "b", (char *) 0, /* 8*/
- /*ch*/ "ch", (char *) 0, /*10*/
- /*d*/ "d", (char *) 0, /*12*/
- /*e*/ "e", (char *) 0, /*14*/
- /*E*/ "e", "ee", "ie", (char *) 0, /*18*/
- /*f*/ "f", "ph", "gh", (char *) 0, /*22*/
- /*g*/ "g", (char *) 0, /*24*/
- /*h*/ "h", (char *) 0, /*26*/
- /*i*/ "i", "e", (char *) 0, /*29*/
- /*I*/ "i", "ai", (char *) 0, /*32*/
- /*i'*/ "i", "ei", (char *) 0, /*35*/
- /*j*/ "j", "g", (char *) 0, /*38*/
- /*k*/ "k", "c", (char *) 0, /*41*/
- /*l*/ "l", (char *) 0, /*43*/
- /*m*/ "m", (char *) 0, /*45*/
- /*n*/ "n", (char *) 0, /*47*/
- /*ng*/ "ng", (char *) 0, /*49*/
- /*o*/ "o", "a", "ah", (char *) 0, /*53*/
- /*O*/ "o", "oh", (char *) 0, /*56*/
- /*oo*/ "oo", "u", (char *) 0, /*59*/
- /*OO*/ "oo", "w", (char *) 0, /*62*/
- /*p*/ "p", (char *) 0, /*64*/
- /*qu*/ "qu", (char *) 0, /*66*/
- /*r*/ "r", (char *) 0, /*68*/
- /*s*/ "s", "c", (char *) 0, /*71*/
- /*sh*/ "sh", "s", (char *) 0, /*74*/
- /*t*/ "t", (char *) 0, /*76*/
- /*th*/ "th", (char *) 0, /*78*/
- /*TH*/ "th", (char *) 0, /*80*/
- /*u*/ "u", (char *) 0, /*82*/
- /*U*/ "u", "oo", (char *) 0, /*85*/
- /*v*/ "v", (char *) 0, /*87*/
- /*x*/ "x", (char *) 0, /*89*/
- /*y*/ "y", (char *) 0, /*91*/
- /*z*/ "z", "s", (char *) 0, /*94*/
- };
-
- struct graph {
- char *graph;
- char type;
- #define CONSONANT 0
- #define VOWEL_LONG 1
- #define VOWEL_SHORT 2
- #define VOWEL_OTHER 3
- #define VOWEL_MASK 3
- #define iscons(c) (((c)->type & VOWEL_MASK) == 0)
- #define isvowel(c) (((c)->type & VOWEL_MASK) != 0)
- /* char frequency; */ /* unused for now */
- char **spellings;
- /* struct graph **following; */ /* maybe later */
- } graph[] = {
- "a", VOWEL_SHORT, &spelling[0],
- "A", VOWEL_LONG, &spelling[2],
- "b", CONSONANT, &spelling[6],
- "ch", CONSONANT, &spelling[8],
- "d", CONSONANT, &spelling[10],
- "e", VOWEL_SHORT, &spelling[12],
- "E", VOWEL_LONG, &spelling[14],
- "f", CONSONANT, &spelling[18],
- "g", CONSONANT, &spelling[22],
- "h", CONSONANT, &spelling[24],
- "i", VOWEL_SHORT, &spelling[26],
- "I", VOWEL_LONG, &spelling[29],
- "i'", VOWEL_OTHER, &spelling[32],
- "j", CONSONANT, &spelling[35],
- "k", CONSONANT, &spelling[38],
- "l", CONSONANT, &spelling[41],
- "m", CONSONANT, &spelling[43],
- "n", CONSONANT, &spelling[45],
- "ng", CONSONANT, &spelling[47],
- "o", VOWEL_SHORT, &spelling[49],
- "O", VOWEL_LONG, &spelling[53],
- "oo", VOWEL_SHORT, &spelling[56],
- "OO", VOWEL_LONG, &spelling[59],
- "p", CONSONANT, &spelling[62],
- "qu", CONSONANT, &spelling[64],
- "r", CONSONANT, &spelling[66],
- "s", CONSONANT, &spelling[68],
- "sh", CONSONANT, &spelling[71],
- "t", CONSONANT, &spelling[74],
- "th", CONSONANT, &spelling[76],
- "TH", CONSONANT, &spelling[78],
- "u", VOWEL_SHORT, &spelling[80],
- "U", VOWEL_LONG, &spelling[82],
- "v", CONSONANT, &spelling[85],
- "x", CONSONANT, &spelling[87],
- "y", CONSONANT, &spelling[89],
- "z", CONSONANT, &spelling[91],
- 0, 0, &spelling[94],
- };
-
- struct graph *vowel[] = {
- &graph[0], &graph[1], &graph[5], &graph[6],
- &graph[10], &graph[11], &graph[12], &graph[19],
- &graph[20], &graph[21], &graph[22], &graph[30],
- &graph[31],
- (struct graph *) 0,
- };
-
- struct graph *consonant[] = {
- &graph[2], &graph[3], &graph[4], &graph[7],
- &graph[8], &graph[9], &graph[13], &graph[14],
- &graph[15], &graph[16], &graph[17], &graph[18],
- &graph[23], &graph[24], &graph[25], &graph[26],
- &graph[27], &graph[28], &graph[29], &graph[32],
- &graph[33], &graph[34], &graph[35],
- (struct graph *) 0,
- };
-
- /*
- * Randomly select a graph from the specifield array. Eventually, this should
- * account for graph frequencies as well.
- */
-
- struct graph *selgraph(graphs)
- struct graph **graphs;
- {
- register int cnt;
-
- for (cnt = 0; graphs[cnt] != (struct graph *) 0; cnt++)
- ;
- return graphs[RANDOM(cnt)];
- }
-
- /*
- * Randomly select a spelling for the specified graph. This is not linear:
- * earlier spellings are preferred over later ones, but the latter do
- * sometimes sneak in.
- */
-
- char *selspell(graph)
- struct graph *graph;
- {
- register int cnt, sel;
-
- for (cnt = 0; graph->spellings[cnt] != (char *) 0; cnt++)
- ;
- if (cnt == 0) {
- fprintf(stderr, "PANIC: selspell(%s) got count(spellings) == 0\n", graph->graph);
- exit(2);
- }
- if (cnt == 1)
- return *graph->spellings;
- /*
- * This may not be the best way to do it... maybe Weemba'd care to lend a
- * hand here? After all, my specialty is programming, NOT math.
- */
- if ((sel = cnt - (int) sqrt((double) RANDOM(cnt * cnt) + 1) - 1) < 0 || sel >= cnt) {
- #ifdef BUGCATCH
- fprintf(stderr, "PANIC: selspell(%s) got nlrand(%d) == %d\n", graph->graph, cnt, sel);
- exit(2);
- #else
- sel = 0;
- #endif
- }
- return graph->spellings[sel];
- }
-
- /*
- * Choose the next source for a graph. The rules are: a consonant MUST be
- * followed by a vowel; a vowel may be followed by a vowel of a different
- * type or by a consonant, but never more than two consecutive vowel graphs.
- */
-
- choosenext(cur, prev)
- {
- if (cur == CONSONANT)
- return VOWEL_MASK;
- else if (prev == -1 || (prev & VOWEL_MASK) != 0)
- return CONSONANT;
- else if (RANDOM(10) == 5)
- return VOWEL_MASK;
- else
- return CONSONANT;
- }
-
- /*
- * We are passed an array of (struct graph *); choose an entry randomly and
- * assemble a string fitting the size constraint. We use the original (OSI)
- * paradigm: alternate consonants and vowels, with the option of two vowels
- * in a row occasionally. The only difference is that they must be different
- * *types* of vowels, a distinction that the OSI version didn't consider.
- */
-
- pwgen(initial, pw, maxlen)
- struct graph **initial;
- char *pw;
- {
- int pwlen, state, prev, tmp;
- struct graph *graph;
- char *spelling;
-
- pwlen = 0;
- state = initial[0]->type;
- prev = -1;
- while (pwlen < maxlen - 1) {
- do {
- graph = selgraph(initial);
- } while (state != CONSONANT && graph->type == prev);
- if ((spelling = selspell(graph)) == (char *) 0) {
- fprintf(stderr, "PANIC: got NULL in selspell(%s)\n", graph->graph);
- exit(2);
- }
- strcpy(pw, spelling);
- while (*pw != '\0')
- pwlen++, pw++;
- tmp = prev;
- prev = graph->type;
- if ((state = choosenext(prev, tmp)) == CONSONANT)
- initial = consonant;
- else
- initial = vowel;
- }
- }
-
- /*
- * ============================== End of pwgen ==============================
- */
-
- #include <pwd.h>
- #include <ctype.h>
- #include <sys/types.h>
- #include <sys/stat.h>
-
- #define TACPWFILE "./pwfile"
- #define TMPPWFILE "/tmp/tacpasswd.XXXXXX"
- #define PW_NAMLEN 8 /* max length user name */
- #define PW_PWDLEN 8 /* max length password */
- #define GEN_PWDLEN 8 /* length of gen passwd */
- #define SPACE_PASSWD 20 /* room allocated for enc passwd */
- #define SPACE_DATE 20 /* room allocated for exp date */
- #define SHORTTIME 30
- #define LONGTIME 180
-
- #define FSEEK_F_BEGIN 0
- #define FSEEK_F_CURR 1
- #define FSEEK_F_END 2
-
- /*
- * Global storage
- */
- char *whoami;
- FILE *pwfile, *tmppwfile;
-
- /*
- * Forward declarations
- */
- char *mktemp(), *getpass(), *crypt();
- struct passwd *fgetpwent();
-
- /*
- * my_exit
- *
- * Close and remove the temporary file if necessary, and then exit.
- */
- my_exit(code)
- int code;
- {
- if (tmppwfile)
- fclose(tmppwfile);
- exit(code);
- }
-
- /*
- * valid
- *
- * Validae this name/password pair against the current record.
- */
- valid(entry, name, word)
- struct passwd *entry;
- char *name, *word;
- {
- char salt[2];
-
- if (strcmp(name, entry->pw_name) != 0)
- return(0);
-
- strncpy(salt, entry->pw_passwd, 2);
- if (strcmp(entry->pw_passwd, crypt(word, salt)) != 0)
- return(0);
-
- return(1);
- }
- /*
- * update_expiration
- *
- * Update the expiration date on this entry. Passwords expire 180 days
- * after they are initially set or changed.
- */
- update_expiration(entry)
- struct passwd *entry;
- {
- long now, day, year, delta;
- char month[10], timestring[26];
-
- if (entry->pw_shell) {
- if (sscanf(entry->pw_shell, "%s %d %d, %d", month, &day, &year, &delta) != 4)
- return(0);
- free(entry->pw_shell);
- } else
- return(0);
-
- if (delta != 180)
- return(0);
-
- time(&now);
- now += delta * 24 * 60 * 60;
- strcpy(timestring, ctime(&now));
-
- entry->pw_shell = (char *)malloc(SPACE_DATE);
- sprintf(entry->pw_shell, "%6.6s %4.4s, %d", timestring+4, timestring+20,
- delta);
-
- return(1);
- }
-
- /*
- * make_passwd
- *
- * This routine sets up and calls the password generation routines. It
- * encrypts the result, and stores it in the password field of the
- * structure.
- */
- make_passwd(entry)
- struct passwd *entry;
- {
- char genpasswd[20], salt[2];
-
- pwgen((RANDOM(10) < 4? vowel: consonant), genpasswd, GEN_PWDLEN);
-
- salt[0] = '\0';
- salt[1] = '\0';
- while (!isalpha(salt[0]))
- salt[0] = random() & 0x7f ;
- while (!isalpha(salt[1]))
- salt[1] = random() & 0x7f ;
- if (entry->pw_passwd)
- free(entry->pw_passwd);
- entry->pw_passwd = (char *)malloc(SPACE_PASSWD);
- strcpy(entry->pw_passwd, crypt(genpasswd, salt));
- printf("New password for %s is \"%s\"\n", entry->pw_name, genpasswd);
- }
-
- /*
- * copy_pwfile1
- *
- * Copy all entries in the password file before the one we are interested in.
- */
- struct passwd *
- copy_pwfile1(name)
- char *name;
- {
- struct passwd *current;
-
- fseek(pwfile, 0, FSEEK_F_BEGIN);
- while (!feof(pwfile)) {
- current = fgetpwent(pwfile);
- if (current && strncmp(current->pw_name, name, PW_NAMLEN) != 0) {
- if (putpwent(current, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put current entry for %0.*s\n",
- whoami, PW_NAMLEN, current->pw_name);
- perror(whoami);
- my_exit(1);
- }
- } else {
- return(current);
- }
- }
- return(NULL);
- }
-
- /*
- * copy_pwfile2
- *
- * Add the new/modified entry. Copy all entries in the password file after
- * the one we are interested in.
- */
- copy_pwfile2(new)
- struct passwd *new;
- {
- struct passwd *current;
-
- if (putpwent(new, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put new entry for %0.*s\n",
- whoami, PW_NAMLEN, new->pw_name);
- perror(whoami);
- my_exit(1);
- }
-
- while (!feof(pwfile)) {
- current = fgetpwent(pwfile);
- if (current && putpwent(current, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put current entry for %0.*s\n",
- whoami, PW_NAMLEN, current->pw_name);
- perror(whoami);
- my_exit(1);
- }
- }
- }
-
- /*
- * copy_back
- *
- * Copy the new file back into the original file. Can't use rename,
- * because the files aren't guaranteed to be on the same file system.
- */
- copy_back()
- {
- char buffer[4096];
- int count;
-
- fseek(pwfile, 0, FSEEK_F_BEGIN);
- fseek(tmppwfile, 0, FSEEK_F_BEGIN);
-
- while ((count = fread(buffer, sizeof(char), 4096, tmppwfile)) > 0)
- fwrite(buffer, sizeof(char), count, pwfile);
-
- fclose(pwfile);
- fclose(tmppwfile);
- }
-
- /*
- * getuser
- *
- * Get a username and password. Fail upon end of file.
- */
- getuser(name, data)
- char *name, *data;
- {
- char buffer[100];
- int i;
-
- name[0] = '\0';
- while (strlen(name) == 0) {
- printf("TACACS Username: ");
- if (!fgets(buffer, 100, stdin) || feof(stdin)) {
- perror(whoami);
- return(0);
- }
- strncpy(name, buffer, PW_NAMLEN);
- for (i = 0; i < PW_NAMLEN; i++)
- if (name[i] == '\n')
- name[i] = '\0';
- name[PW_NAMLEN] = '\0';
- }
-
- strncpy(data, getpass("Old Password: "), PW_PWDLEN);
- if (feof(stdin))
- return(0);
-
- return(1);
- }
-
- /*
- * waitforuser
- *
- * Wait for the user to say he is done.
- */
- waitforuser()
- {
- printf("Hit the 'return' key to exit.");
- getchar();
- }
-
- /*
- * init
- *
- * Initialize the program.
- */
- init(argc, argv)
- int argc;
- char **argv;
- {
- char *c, *tmppwfilename, template[100];
-
- whoami = argv[0];
-
- /*
- * Open TAC password file, and scratch file
- */
- if (pwfile)
- fclose(pwfile);
- if ((pwfile = fopen(TACPWFILE, "a+")) == NULL) {
- fprintf(stderr, "%s: cannot open TACACS password file\n", whoami);
- perror(whoami);
- my_exit(1);
- }
- strcpy(template, TMPPWFILE);
- tmppwfilename = mktemp(template);
- if (tmppwfile)
- fclose(tmppwfile);
- if ((tmppwfile = fopen(tmppwfilename, "w+")) == NULL) {
- fprintf(stderr, "%s: cannot open temporary file %s\n", whoami, tmppwfilename);
- perror(whoami);
- my_exit(1);
- }
- if (unlink(tmppwfilename) != 0) {
- fprintf(stderr, "%s: cannot unlink temporary file %s\n", whoami, tmppwfilename);
- perror(whoami);
- my_exit(1);
- }
-
- srandom(time(0) + (getpgrp() << 8) + getpid());
- }
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- struct passwd *entry;
- char username[10], password[10];
- int i, done;
-
- for (i = 0; i < 3; i++) {
- init(argc, argv);
-
- if (!getuser(username, password))
- return(1);
-
- entry = copy_pwfile1(username);
- if (!entry) {
- printf("I cannot find a user named '%s' in the TACACS database.\n", username);
- printf("Please contact cisco Enginering for assistance.\n");
- my_exit(1);
- }
-
- if (!valid(entry, username, password)) {
- printf("You typed an incorrect username/password combinaion.\n");
- printf("Please try again\n\n");
- continue;
- }
-
- if (!update_expiration(entry)) {
- printf("You are not allowed to change your password and revalidate\n");
- printf("your account. Please contact cisco Engineering for assistance.\n");
- my_exit(1);
- }
-
- make_passwd(entry);
- copy_pwfile2(entry);
- copy_back();
- waitforuser();
- my_exit(0);
- }
- my_exit(0);
- }
-